package com.googlecode.classgrep.info;

import java.lang.reflect.Constructor;
import java.lang.reflect.Method;

import org.apache.commons.lang.ArrayUtils;
import org.objectweb.asm.Type;

public class InvokeInfo {

	private Class<?> callingFunOwner;

	private FunctionInfo callingFun;

	private Class<?>[] calledFunActuallyArgTypes;

	private FunctionInfo calledFun;
	
	private int lineNumber;

	public InvokeInfo(Class<?> callingFunOwner, FunctionInfo callingFun,
			FunctionInfo calledFun, Class<?>[] calledFunActuallyArgTypes,
			int lineNumber) {
		super();
		this.callingFunOwner = callingFunOwner;
		this.callingFun = callingFun;
		this.calledFunActuallyArgTypes = calledFunActuallyArgTypes;
		this.calledFun = calledFun;
		this.lineNumber = lineNumber;
	}

	public Class<?> getCallingFunOwner() {
		return callingFunOwner;
	}

	public FunctionInfo getCallingFun() {
		return callingFun;
	}

	public Class<?>[] getCalledFunActuallyArgTypes() {
		return calledFunActuallyArgTypes;
	}

	public FunctionInfo getCalledFun() {
		return calledFun;
	}
	

	public int getLineNumber() {
		return lineNumber;
	}
	

	/**
	 * 
	 *
	 */
	public static class FunctionInfo {

		private FunctionType type;

		private Class<?>[] parameterTypes;

		private String name;
		
		private Object original;

		private FunctionInfo(){}
		
		public FunctionType getType() {
			return type;
		}

		private void setType(FunctionType type) {
			this.type = type;
		}

		public Class<?>[] getParameterTypes() {
			return parameterTypes;
		}

		private void setParameterTypes(Class<?>[] parameterTypes) {
			this.parameterTypes = parameterTypes;
		}

		public String getName() {
			return name;
		}

		private void setName(String name) {
			this.name = name;
		}

		public Object getOriginal() {
			return original;
		}

		private void setOriginal(Object original) {
			this.original = original;
		}
		
		public String getDescription(){
			if(original instanceof Method){
				return Type.getMethodDescriptor((Method)original);
			}else if(original instanceof Constructor){
				return Type.getConstructorDescriptor((Constructor<?>)original);
			}else{
				return "()V";
			}
		}
		
		public Class<?> getDeclaringClass(){
			if(original instanceof Method){
				return ((Method)original).getDeclaringClass();
			}else if(original instanceof Constructor){
				return ((Constructor<?>)original).getDeclaringClass();
			}else{
				return (Class<?>)original;
			}
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + ((original == null) ? 0 : original.hashCode());
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			FunctionInfo other = (FunctionInfo) obj;
			if (original == null) {
				if (other.original != null)
					return false;
			} else if (!original.equals(other.original))
				return false;
			return true;
		}

		@Override
		public String toString() {
			return "{type:\"" + type + "\", original:\"" + original + "\"}";
		}
		

	}
	
	/**
	 * 
	 * @param method
	 * @return
	 */
	public static FunctionInfo buildCommonFunctionInfo(Method method){
		FunctionInfo info = new FunctionInfo();
		info.setName(method.getName());
		info.setParameterTypes(method.getParameterTypes());
		info.setType(FunctionType.Method);
		info.setOriginal(method);
		return info;
	}
	
	/**
	 * 
	 */
	public static FunctionInfo buildConstructorFunctionInfo(Constructor<?> cost){
		FunctionInfo info = new FunctionInfo();
		info.setName("<init>");
		info.setParameterTypes(cost.getParameterTypes());
		info.setType(FunctionType.Method);
		info.setOriginal(cost);
		return info;
	}
	
	/**
	 * 
	 * @return
	 */
	public static FunctionInfo buildStaticFunctionInfo(Class<?> clazz){
		FunctionInfo info = new FunctionInfo();
		info.setName("<cinit>");
		info.setType(FunctionType.Constructor);
		info.setOriginal(clazz);
		return info;
	}
	
	

	public static enum FunctionType {
		Static, Constructor, Method
	}

	@Override
	public String toString() {
		StringBuilder argString = new StringBuilder("[");
		if(ArrayUtils.isNotEmpty(calledFunActuallyArgTypes)){
			for(Class<?> ac : calledFunActuallyArgTypes){
				argString.append("\"").append(ac).append("\",");
			}
			argString.deleteCharAt(argString.length() - 1);
		}
		argString.append("]");
		
		return "{callingFunOwner:\"" + callingFunOwner + 
			   "\", callingFun:" + callingFun + 
			   ", calledFun:" + calledFun + 
			   ", calledFunActuallyArgTypes:" +  argString + 
			   ",lineNumber : " + lineNumber + "}";
	}

}
